package jwtx

import (
	"encoding/json"
	"time"

	"gitee.com/yanwc/gozero-utils/errx"
	"github.com/duke-git/lancet/v2/maputil"
	jwt "github.com/golang-jwt/jwt/v5"
)

type TokenOption struct {
	Exp *jwt.NumericDate
	Sub string
	Aud string
	Iss string
}

const (
	defaultExpDurationSeconds = 60 * 60 * 8
)

type Option func(*TokenOption)

func NewToken(data JwtTokenData, secret string, opts ...Option) (string, error) {
	opt := TokenOption{
		Exp: &jwt.NumericDate{Time: time.Now().Add(time.Second * time.Duration(defaultExpDurationSeconds))},
		Sub: "uhk-system",
		Iss: "uhk",
		Aud: "uhk",
	}

	for _, o := range opts {
		o(&opt)
	}

	claims := jwt.MapClaims{
		"data": data,
		"exp":  opt.Exp,
		"iss":  opt.Iss,
		"sub":  opt.Sub,
		"aud":  opt.Aud,
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(secret))
}

func NewTokenWithExp(data JwtTokenData, key string) (string, error) {
	return NewToken(data, key)
}

func ParseToken[T any](tokenString string, secret string, target *T) *errx.Error {
	token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
		return []byte(secret), nil
	})
	if err != nil {
		return errx.NewWithError(errx.TOKEN_PARSER, err)
	}

	if !token.Valid {
		return errx.New(errx.TOKEN_VALID)
	}

	if claims, ok := token.Claims.(jwt.MapClaims); ok {
		t := claims["data"]
		if v, ok := t.(string); ok {
			err = json.Unmarshal([]byte(v), target)
			if err != nil {
				return errx.NewWithError(errx.JSON_UNMARSHAL, err)
			}
		} else {
			maputil.MapToStruct(t.(map[string]interface{}), target)
		}
		return nil
	} else {
		return nil
	}
}

func Valid(value string, secret string) (bool, error) {
	token, err := jwt.Parse(value, func(t *jwt.Token) (interface{}, error) {
		return []byte(secret), nil
	})
	if err != nil {
		return false, err
	}

	return token.Valid, nil
}

func WithSubOption(sub string) Option {
	return func(to *TokenOption) {
		to.Sub = sub
	}
}

func WithIssOption(iss string) Option {
	return func(to *TokenOption) {
		to.Iss = iss
	}
}

func WithExpOption(expDurationSeconds int64) Option {
	return func(to *TokenOption) {
		to.Exp = &jwt.NumericDate{Time: time.Now().Add(time.Second * time.Duration(expDurationSeconds))}
	}
}

func WithAudOption(aud string) Option {
	return func(to *TokenOption) {
		to.Aud = aud
	}
}
